package org.mbds.sensorsdatacollector.android.tools;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

import org.mbds.sensorsdatacollector.android.SensorsDataCollectorApplication;
import org.mbds.sensorsdatacollector.android.activity.MainActivity;
import org.mbds.sensorsdatacollector.android.config.Properties;
import org.mbds.sensorsdatacollector.android.db.SensorData;
import org.mbds.sensorsdatacollector.android.db.SensorsDataSQLiteDatabase;

import android.content.Context;
import android.database.Cursor;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;


public class SensorsDataManager implements SensorEventListener {

	public static ArrayList<Float> y_val;	
	public static ArrayList<Float> pics_val;
	private boolean isRegistered;
	private Sensor sensor; // The particular sensor that will be created.
	private long lastTime = 0; // Last time that the data has been updated.
	private int sensorType; // Type of sensor.
	private SensorManager sm; // Each sensor class will have a private sensor manager 
	private ArrayList<SensorData> sensorDatas; //Data collected by the sensor.
	private ArrayList<SensorObserverInterface> sensorObs; // Pattern Observer : list of classes that want to be notify when data changes for a sensor.
	private int sensorDelay; //Frequency of data collection.
	private double delta;
	private boolean isHeader = true;
	// Data collection
	private double timeFromStart = 0;
	private boolean isCollectData = false;
	private String nameOfFile="";
	

	private static float[] orientation = null; //Orientation sensor data array
	private static float[] rotation = null; // Rotation data array
	private static float[] rotationMatrix = null; // Rotation matrix data.
	
	public final static double MULT_NANO = 0.000000001D;
	private String currentDateandTime ;
	private String m_activity;
	// How many time we collect data?
	private final static double NB_SECONDS_TO_COLLECT = 1d;
	private final static double NB_SECONDS_TO_SAVE = 60d;
	private double nbSecondsCollected = 0;
	private double nbSecondsSaved = 0;
	
	
	/*
	 * Different types of sensors available in an Android device.
	 */
	public final static int SENSOR_TYPE_ACCELEROMETER = Sensor.TYPE_ACCELEROMETER; 
	public final static int SENSOR_TYPE_GYROSCOPE = Sensor.TYPE_GYROSCOPE; 
	public final static int SENSOR_TYPE_MAGNETIC_FIELD = Sensor.TYPE_MAGNETIC_FIELD;
	public final static int SENSOR_TYPE_GRAVITY = Sensor.TYPE_GRAVITY;
	public final static int SENSOR_TYPE_LINERA_ACCEL = Sensor.TYPE_LINEAR_ACCELERATION;
	public final static int SENSOR_TYPE_ROTATION_VECTOR = Sensor.TYPE_ROTATION_VECTOR;
	public final static int SENSOR_TYPE_PRESSURE = Sensor.TYPE_PRESSURE;
	public final static int SENSOR_TYPE_PROXIMITY = Sensor.TYPE_PROXIMITY;

	// magnetic field sensor data manager
//	public static SensorsDataManager magneticFieldData = new SensorsDataManager(
//			SensorsDataManager.SENSOR_TYPE_MAGNETIC_FIELD);
	// accelerometer sensor data manager
	public static SensorsDataManager accelerometerData = new SensorsDataManager(
			SensorsDataManager.SENSOR_TYPE_ACCELEROMETER);
	// gravity sensor data manager
//	public static SensorsDataManager gravityData = new SensorsDataManager(
//			SensorsDataManager.SENSOR_TYPE_GRAVITY);
//	// linear acceleration sensor data manager
//	public static SensorsDataManager linearAccelData = new SensorsDataManager(
//			SensorsDataManager.SENSOR_TYPE_LINERA_ACCEL);
//	// rotation sensor data manager
//	public static SensorsDataManager rotationData = new SensorsDataManager(
//			SensorsDataManager.SENSOR_TYPE_ROTATION_VECTOR);
//	// Gyroscope sensor data manager
//	public static SensorsDataManager gyroscopeData = new SensorsDataManager(
//	SensorsDataManager.SENSOR_TYPE_GYROSCOPE);
//	// Pressure sensor data manager
//	public static SensorsDataManager pressureData = new SensorsDataManager(
//	SensorsDataManager.SENSOR_TYPE_PRESSURE);
//	// Proximity sensor data manager
//	public static SensorsDataManager proximityData = new SensorsDataManager(
//	SensorsDataManager.SENSOR_TYPE_PROXIMITY);

	/**
	 * Data manager constructor
	 * @param sensorType
	 */
	public SensorsDataManager(int sensorType) {
		sm = (SensorManager) SensorsDataCollectorApplication.getAppContext()
				.getSystemService(Context.SENSOR_SERVICE);
		this.sensorType = sensorType;
		FileUtilities.deleteFile(Properties.SD_DIRECTORY + getSensorTypeName() + ".txt");
		sensorDatas = new ArrayList<SensorData>();
		sensorObs = new ArrayList<SensorObserverInterface>();
		isRegistered = false;
		sensor = sm.getDefaultSensor(sensorType);
		y_val = new ArrayList<Float>();
		pics_val = new ArrayList<Float>();
		
		
		
		//if(MainActivity.TYPE_DELAY == "Normal")
			this.sensorDelay = Properties.SENSOR_DELAY;
		/*else if (MainActivity.TYPE_DELAY == "UI")
			this.sensorDelay = Properties.SENSOR_DELAY_2;
		else 
			this.sensorDelay = Properties.SENSOR_DELAY_1; */
		
		
	}

	public void register() {
		if (isRegistered) {
			sm.unregisterListener(this);
			isRegistered = false;
		}
		if (sensor!=null) {
			sm.registerListener(this, sensor, sensorDelay);
			isRegistered = true;
		}
	}
	
	public void unregister() {
		sensorObs.clear();
		if (isRegistered) { 
			sm.unregisterListener(this, sensor);
			isRegistered = false;
		}
	}
	
	@Override
	public void finalize() {
		unregister();
		try {
			super.finalize();
		} catch (Throwable t) {
			t.printStackTrace();
		}
	}
	
	public boolean addObserver(SensorObserverInterface obs) {
		boolean res = true;
		if (!sensorObs.contains(obs))
			res = sensorObs.add(obs);
		register();
		return res;
	}

	public boolean removeObserver(SensorObserverInterface obs) {
		boolean res = false;
		if (sensorObs.size()>0)
			if (sensorObs.contains(obs))
				res = sensorObs.remove(obs);
		// No more observer: unregister
		if (sensorObs.size()==0) {
			unregister();
		}
		return res;
	}
	
	/**
	 * Start data collection
	 * @param activity
	 */
	public void startDataCollection(String activity) {
		isCollectData=true;
		timeFromStart=0;
		lastTime = 0;
		// Collect CSV data
		m_activity = activity;
		String header = "Time;X;Y;Z"+"\n";
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH:mm:ss");
		currentDateandTime = sdf.format(new Date());
		nameOfFile = Properties.SD_DIRECTORY + currentDateandTime+"_" +StaticCommons.getDeviceName()+"_" +activity+"_" +getSensorTypeName() + ".csv";
		FileUtilities.saveDataToFile(nameOfFile,header);
		
	}

	/**
	 * Stop data collection
	 */
	public void stopDataCollection() {
		//retrieveDataFromDB();
		isCollectData=false;
		//retrieveDataFromDB();
	}

	public void addSensorData(SensorData sensorData) {
		if (lastTime==0)
			lastTime = sensorData.time;
		delta = (sensorData.time - lastTime) * MULT_NANO;
		setLastTime(sensorData.time);

		// Get orientation from magnetic fields and accelerometer (not accurate)
//		if (sensorType==SENSOR_TYPE_MAGNETIC_FIELD) {// Process only once
//			if (accelerometerData.getSensorDatas().size() > 0) {
//				SensorData accel =accelerometerData.getSensorDatas().get(
//						accelerometerData.getSensorDatas().size() - 1);
//				float[] magnetoValues = sensorData.val;
//				float[] acceleration = accel.val;
//				float R[] = new float[9];
//				float I[] = new float[9];
//				success = SensorManager.getRotationMatrix(R, I,
//						acceleration, magnetoValues);
//				if (success) {
//					orientation = SensorManager.getOrientation(R, new float[3]);
//				}
//			}
//		}
		
		// get orientation from rotation vector (more accurate)
		if (sensorType==SENSOR_TYPE_ROTATION_VECTOR) {
			rotation=sensorData.val.clone();
			float vec[] = sensorData.val.clone();
			float quat[] = new float[4]; //We've set 4 instead of 16 as the array length because of an error raised by Android when compiling
			SensorManager.getQuaternionFromVector(quat, vec);
			float [] RotMat = new float[16];
			SensorManager.getRotationMatrixFromVector(RotMat, quat);
			rotation = RotMat;
			if (rotationMatrix==null)
				rotationMatrix = new float[16];
			// Update rotation matrix according gravity & gyroscope
			SensorManager.getRotationMatrixFromVector(rotationMatrix, sensorData.val);
			// Remap coordinates when mobile is rotated
			SensorManager.remapCoordinateSystem(rotationMatrix,	SensorManager.AXIS_X, SensorManager.AXIS_MINUS_X, rotationMatrix);		
			if (orientation==null)
				orientation = new float[3];
			orientation = SensorManager.getOrientation(rotationMatrix, orientation);
		}
		
		if (rotation!=null)
			sensorData.rotation = rotation;
		
		// Set orientation 
		if (orientation!=null) {
			sensorData.setOrientation(orientation);
		}


		if (isCollectData) {
			// Save data in CSV format
			String data = timeFromStart + ";" + sensorData.val[0] + ";"
					+ sensorData.val[1] + ";" + sensorData.val[2];
			//data = data.replace(".", ",");
			//FileUtilities.saveDataToFile(Properties.SD_DIRECTORY + getSensorTypeName() + ".csv",	data);
			FileUtilities.saveDataToFile(nameOfFile,data);
		}
		
		//insertDataIntoDB(sensorData.val, delta);
		if (1==0)//
		insertDataIntoDB(sensorData.val, timeFromStart);
		
		
		timeFromStart+=delta;
		nbSecondsCollected+=delta;

		sensorDatas.add(sensorData);

		// Send the arraylist of values after 1 second
		if (nbSecondsCollected>=NB_SECONDS_TO_COLLECT) {
			for (SensorObserverInterface obs : sensorObs) {
				// clone data to avoid object clearing
				ArrayList<SensorData> data = (ArrayList<SensorData>) sensorDatas.clone();
				synchronized(data) {
					obs.updateSensorData(data, delta, sensorData);
				}
			}
			nbSecondsCollected=0;
			// keep only one second data
			sensorDatas = new ArrayList<SensorData>();
		}
	}
	
	public SensorData getLastData() {
		int size = sensorDatas.size();
		if (size == 0) {
			return null;
		}
		return sensorDatas.get(size);
	}

	public long getLastTime() {
		return lastTime;
	}

	private void setLastTime(long lastTime) {
		this.lastTime = lastTime;
	}

	public ArrayList<SensorData> getSensorDatas() {
		return sensorDatas;
	}

	public int getSensorType() {
		return sensorType;
	}

	public void setSensorType(int type) {
		sensorType = type;
	}

	public String getSensorTypeName() {
		return getSensorTypeName(sensorType);
	}

	public static String getSensorTypeName(int type) {
		String name = "UNKNOWN";
		switch (type) {
		case SENSOR_TYPE_ACCELEROMETER:
			name = "Accelerometer";
			break;
		case SENSOR_TYPE_GYROSCOPE:
			name = "Gyroscope";
			break;
		case SENSOR_TYPE_MAGNETIC_FIELD:
			name = "Magnetic field";
			break;
		case SENSOR_TYPE_GRAVITY:
			name = "Gravity";
			break;
		case SENSOR_TYPE_PRESSURE:
			name = "Pressure";
			break;
		case SENSOR_TYPE_PROXIMITY:
			name = "Proximity";
			break;
		case SENSOR_TYPE_LINERA_ACCEL:
			name = "Linear acceleration";
			break;
		case SENSOR_TYPE_ROTATION_VECTOR:
			name = "Rotation vector";
			break;
		}
		return name;
	}

	public void clear() {
		sensorDatas.clear();
	}

	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// TODO Auto-generated method stub
	}

	public void onSensorChanged(SensorEvent event) {
		// Values from sensor
		addSensorData(new SensorData(event.timestamp, event.values));
		//notifyObserver(datas, event.timestamp, new SensorData(event.timestamp, event.values));
	}
	/*
	private void notifyObserver(ArrayList<SensorData> datas, double time, SensorData lastValues){
		try {
			if(sensorObs!=null){
				for (int i = 0; i < sensorObs.size(); i++) {
					sensorObs.get(i).updateSensorData(datas, time, lastValues);
				}
			}
			
		} catch (Exception e) {
			// TODO: handle exception
		}
	}*/
	

	public void setDelay(int delay) {
		this.sensorDelay = delay;
		unregister();
		register();
	}
	
	public void insertDataIntoDB(float[] val, double delta) {
		String sensorLabel = getSensorTypeName();
		String deviceName = StaticCommons.getDeviceName();
		String sqlStatement = SensorsDataSQLiteDatabase.SQL_INSERT_DATA.replace("%DATE%",currentDateandTime).replace("%DEVICE%", 
				deviceName).replace("%DELAY%", sensorDelay+"").replace("%SENSOR%", sensorLabel).replace("%DELTATIME%", 
						delta+"").replace("%X%", val[0]+"").replace("%Y%", val[1]+"").replace("%Z%", val[2]+"");
		SensorsDataSQLiteDatabase.executeSQLStatment(sqlStatement);
		// TODO => Delete old data (older than 1 minute)
	}
	
	public static int retrieveDataFromDB()
	{
		int nb_elem = 0;	
		int nbrePics = 0;
		String sqlStatement = "select y from data";		
		Cursor crs = SensorsDataSQLiteDatabase.query(sqlStatement);
		
		crs.moveToFirst();
		while (crs.isAfterLast()==false){
			//nb_elem = crs.getCount();
			y_val.add(crs.getFloat(0));
			crs.moveToNext();			
		}
		crs.close();
				
	/*	for (int i = 0; i<y_val.size(); i++)
		{
			nb_elem = nb_elem + 1;
		}
		return nb_elem;*/
		
		 for (int i = 1; i<y_val.size() - 1;i++){
			if ( (y_val.get(i-1) < y_val.get(i))  && y_val.get(i) > y_val.get(i+1))
					{
				     nbrePics = nbrePics + 1;
				     //pics_val.get(i) = (y_val.get(i));
					}
						
			}
	 		return nbrePics;
		}
	}	
	